
/*
 * Hlpay-Plus aggregate payment system. Copyright
 * (c) 2022-2023 Hlpay Team Copyright has the right of final interpretation.
 */

package com.hlkj.pay.controller;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import com.hlkj.framework.common.pojo.CommonResult;
import com.hlkj.framework.common.pojo.LocalContext;
import com.hlkj.framework.common.pojo.PageResult;
import com.hlkj.framework.common.util.collection.CollectionUtils;
import com.hlkj.pay.dto.BaseQueryDto;
import com.hlkj.pay.dto.LocalAdminUserRequest;
import com.hlkj.pay.dto.merchant.MerchantAccessInfoPageQueryDto;
import com.hlkj.pay.enums.CommonEnum;
import com.hlkj.pay.enums.PayOrderEnums;
import com.hlkj.pay.infrastructure.model.merchant.MerchantAccessInfoDO;
import com.hlkj.pay.infrastructure.model.merchant.MerchantInfoDO;
import com.hlkj.pay.infrastructure.model.order.OrderFundDetailDO;
import com.hlkj.pay.infrastructure.model.pay.PayChannelDO;
import com.hlkj.pay.infrastructure.model.pay.PayWayDO;
import com.hlkj.pay.infrastructure.model.pay.ProviderInfoDO;
import com.hlkj.pay.service.merchant.IMerchantAccessService;
import com.hlkj.pay.service.merchant.IMerchantService;
import com.hlkj.pay.service.order.IPayOrderFundService;
import com.hlkj.pay.service.pay.IPayChannelService;
import com.hlkj.pay.service.pay.IPayWayService;
import com.hlkj.pay.service.pay.IProviderService;
import com.hlkj.pay.util.DateUtils;
import com.hlkj.pay.vo.*;
import com.hlkj.pay.vo.merchant.resp.app.MerchantFundListResp;
import com.hlkj.pay.vo.order.resp.PayOrderDetailResp;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import lombok.extern.slf4j.Slf4j;

/**
 * @author HlpayTeam
 * @date 2024/09/03 17:03
 */
@Slf4j
public class BaseController<T> {

    @Autowired
    private IMerchantService merchantService;

    @Autowired
    private IProviderService providerService;

    @Autowired
    private IPayChannelService payChannelService;

    @Autowired
    private IPayWayService payWayService;

    @Autowired
    private IMerchantAccessService merchantAccessService;

    @Autowired
    private IPayOrderFundService payOrderFundService;

    /**
     * buildPageResult
     * 
     * @param pageResult
     * @param list
     * @return
     */
    public CommonResult<PageResult<T>> buildPageResult(PageResult pageResult, List<T> list) {
        PageResult<T> result = new PageResult<>();
        result.setPageNo(pageResult.getPageNo());
        result.setPageSize(pageResult.getPageSize());
        result.setPages(pageResult.getPages());
        result.setTotal(pageResult.getTotal());
        result.setList(list);
        return CommonResult.success(result);
    }

    /**
     * buildPageResult
     * 
     * @param list
     * @return
     */
    public void processMerchantList(List<BaseMerchantResp> list) {
        List<String> snList = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getSn())).map(roleResp -> roleResp.getSn()).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(snList)) {
            return;
        }
        List<MerchantInfoDO> merchantInfoList = merchantService.queryList(snList);
        Map<String, MerchantInfoDO> merchantInfoMap = merchantInfoList.stream()
                .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getSn(), Function.identity(), (t1, t2) -> t1));
        list.stream().forEach(roleResp -> {
            if (StringUtils.hasText(roleResp.getSn())) {
                MerchantInfoDO merchantInfoDO = merchantInfoMap.get(roleResp.getSn());
                if (merchantInfoDO != null) {
                    roleResp.setMerchantName(merchantInfoDO.getName());
                }
            }
        });
    }

    /**
     * buildPageResult
     * 
     * @param list
     * @return
     */
    public void processOrderList(List<BaseOrderResp> list) {
        List<String> snList = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getSn())).map(roleResp -> roleResp.getSn()).collect(Collectors.toList());
        List<String> providerList = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getProviderCode())).map(roleResp -> roleResp.getProviderCode())
                .collect(Collectors.toList());
        List<String> channelCodes = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getChannelCode())).map(roleResp -> roleResp.getChannelCode())
                .collect(Collectors.toList());
        List<String> payWayCodes = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getPayWayCode())).map(roleResp -> roleResp.getPayWayCode())
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(snList) && CollectionUtils.isEmpty(providerList)) {
            return;
        }

        List<MerchantInfoDO> merchantInfoList = merchantService.queryList(snList);
        Map<String, MerchantInfoDO> merchantInfoMap = merchantInfoList.stream()
                .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getSn(), Function.identity(), (t1, t2) -> t1));
        List<ProviderInfoDO> providerInfoList = providerService.queryByCodes(providerList);
        Map<String, ProviderInfoDO> providerInfoMap = providerInfoList.stream()
                .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getCode(), Function.identity(), (t1, t2) -> t1));

        List<PayChannelDO> payChannelList = payChannelService.queryByCodes(channelCodes);
        Map<String, PayChannelDO> payChannelInfoMap = payChannelList.stream()
                .collect(Collectors.toMap(payChannelDO -> payChannelDO.getCode(), Function.identity(), (t1, t2) -> t1));
        List<PayWayDO> payWayList = payWayService.queryByCodes(payWayCodes);
        Map<String, PayWayDO> payWayMap = payWayList.stream().collect(Collectors.toMap(payWayDO -> payWayDO.getCode(), Function.identity(), (t1, t2) -> t1));
        list.stream().forEach(roleResp -> {
            MerchantInfoDO merchantInfoDO = merchantInfoMap.get(roleResp.getSn());
            ProviderInfoDO providerInfoDO = providerInfoMap.get(roleResp.getProviderCode());
            PayChannelDO payChannelDO = payChannelInfoMap.get(roleResp.getChannelCode());
            PayWayDO payWayDO = payWayMap.get(roleResp.getPayWayCode());
            if (merchantInfoDO != null) {
                roleResp.setMerchantName(merchantInfoDO.getName());
            }
            if (providerInfoDO != null) {
                roleResp.setProviderName(providerInfoDO.getName());
            }
            if (payChannelDO != null) {
                roleResp.setChannelName(payChannelDO.getName());
            }
            if (payWayDO != null) {
                roleResp.setPayWayName(payWayDO.getName());
            }
        });

    }

    /**
     * buildPageResult
     *
     * @param list
     * @return
     */
    public void processOrderAccessList(List<PayOrderDetailResp> list) {
        List<String> subMchList = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getSubMchId())).map(roleResp -> roleResp.getSubMchId()).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(subMchList)) {
            return;
        }

        MerchantAccessInfoPageQueryDto queryDto = new MerchantAccessInfoPageQueryDto();
        queryDto.setSubMchIdList(subMchList);
        List<MerchantAccessInfoDO> accessInfoDOList = merchantAccessService.queryList(queryDto);
        Map<String, MerchantAccessInfoDO> accessInfoDOMap = accessInfoDOList.stream()
                .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getSubMchId(), Function.identity(), (t1, t2) -> t1));
        list.stream().forEach(roleResp -> {
            MerchantAccessInfoDO accessInfoDO = accessInfoDOMap.get(roleResp.getSubMchId());
            if (accessInfoDO != null) {
                roleResp.setAccessFullName(accessInfoDO.getName());
                roleResp.setAccessShortName(accessInfoDO.getShortName());
            }
        });

    }

    /**
     * buildPageResult
     *
     * @param list
     * @return
     */
    public void processProvideList(List<BaseProviderResp> list) {
        List<String> snList = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getSn())).map(roleResp -> roleResp.getSn()).collect(Collectors.toList());
        List<String> providerList = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getProviderCode())).map(roleResp -> roleResp.getProviderCode())
                .collect(Collectors.toList());
        List<String> channelCodes = list.stream().filter(baseResp -> StringUtils.hasText(baseResp.getChannelCode())).map(roleResp -> roleResp.getChannelCode())
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(snList) && CollectionUtils.isEmpty(providerList)) {
            return;
        }
        List<MerchantInfoDO> merchantInfoList = merchantService.queryList(snList);
        Map<String, MerchantInfoDO> merchantInfoMap = merchantInfoList.stream()
                .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getSn(), Function.identity(), (t1, t2) -> t1));
        List<ProviderInfoDO> providerInfoList = providerService.queryByCodes(providerList);
        Map<String, ProviderInfoDO> providerInfoMap = providerInfoList.stream()
                .collect(Collectors.toMap(merchantInfoDO -> merchantInfoDO.getCode(), Function.identity(), (t1, t2) -> t1));

        List<PayChannelDO> payChannelList = payChannelService.queryByCodes(channelCodes);
        Map<String, PayChannelDO> payChannelInfoMap = payChannelList.stream()
                .collect(Collectors.toMap(payChannelDO -> payChannelDO.getCode(), Function.identity(), (t1, t2) -> t1));
        list.stream().forEach(roleResp -> {
            MerchantInfoDO merchantInfoDO = merchantInfoMap.get(roleResp.getSn());
            ProviderInfoDO providerInfoDO = providerInfoMap.get(roleResp.getProviderCode());
            PayChannelDO payChannelDO = payChannelInfoMap.get(roleResp.getChannelCode());
            if (merchantInfoDO != null) {
                roleResp.setMerchantName(merchantInfoDO.getName());
            }
            if (providerInfoDO != null) {
                roleResp.setProviderName(providerInfoDO.getName());
            }
            if (payChannelDO != null) {
                roleResp.setChannelName(payChannelDO.getName());
            }
        });

    }

    /**
     * buildPageResult
     *
     * @param baseProviderResp
     * @return
     */
    public void processOrder(BaseOrderResp baseProviderResp) {
        MerchantInfoDO merchantInfoDO = merchantService.queryMerchant(baseProviderResp.getSn());
        ProviderInfoDO providerInfoDO = providerService.queryByCode(baseProviderResp.getProviderCode());
        PayChannelDO payChannelDO = payChannelService.queryByCode(baseProviderResp.getChannelCode());
        PayWayDO payWayDO = payWayService.queryByCode(baseProviderResp.getPayWayCode());
        if (merchantInfoDO != null) {
            baseProviderResp.setMerchantName(merchantInfoDO.getName());
        }
        if (providerInfoDO != null) {
            baseProviderResp.setProviderName(providerInfoDO.getName());
        }
        if (payChannelDO != null) {
            baseProviderResp.setChannelName(payChannelDO.getName());
        }
        if (payWayDO != null) {
            baseProviderResp.setPayWayName(payWayDO.getName());
        }
        // 更新分账状态
        baseProviderResp.setAccStatus(queryFundStatus(baseProviderResp.getPayOrderNo(),baseProviderResp.getAmount(),baseProviderResp.getRefundAmount()));
    }


    public void processFundAccount(MerchantFundListResp merchantFundListResp) {
        MerchantInfoDO merchantInfoDO = merchantService.queryMerchant(merchantFundListResp.getSn());
        ProviderInfoDO providerInfoDO = providerService.queryByCode(merchantFundListResp.getProviderCode());
        PayChannelDO payChannelDO = payChannelService.queryByCode(merchantFundListResp.getChannelCode());
        if (merchantInfoDO != null) {
            merchantFundListResp.setMerchantName(merchantInfoDO.getName());
        }
        if (providerInfoDO != null) {
            merchantFundListResp.setProviderName(providerInfoDO.getName());
        }
        if (payChannelDO != null) {
            merchantFundListResp.setChannelName(payChannelDO.getName());
        }
    }

    /**
     * 通过订单号查询分账状态
     * @param payOrderNo
     * @return
     */
    public Integer queryFundStatus(String payOrderNo, BigDecimal orderAmount,BigDecimal refundAmount){

        if(orderAmount == null){
            orderAmount = new BigDecimal(0);
        }
        if(refundAmount == null){
            refundAmount = new BigDecimal(0);
        }
        // 订单金额对冲
        if(orderAmount.subtract(refundAmount).compareTo(new BigDecimal(0)) == 0){
            return PayOrderEnums.PAY_ORDER_FUND_AMOUNT_STATUS.NOT_FUND.getCode();
        }
        // 查询分账信息
        List<OrderFundDetailDO> fundDetailDOList = payOrderFundService.queryPayAcctInfo(payOrderNo);
        if(CollUtil.isEmpty(fundDetailDOList)){
            return PayOrderEnums.PAY_ORDER_FUND_AMOUNT_STATUS.NOT_FUND.getCode();
        }
        // 获取分账成功的金额
        BigDecimal accAmount = sum(fundDetailDOList, PayOrderEnums.PAY_ORDER_FUND_STATUS.SUCCESS);
        // 获取分账退款成功的金额
        BigDecimal accRefundAmount = sum(fundDetailDOList, PayOrderEnums.PAY_ORDER_FUND_STATUS.REFUND);
        // 分账金额对冲
        if(accAmount.subtract(accRefundAmount).compareTo(new BigDecimal(0)) == 0){
            return PayOrderEnums.PAY_ORDER_FUND_AMOUNT_STATUS.PART_FUND.getCode();
        }
        // 订单金额-订单退款金额   比较   分账金额-分账退款金额
        if((orderAmount.subtract(refundAmount)).compareTo(accAmount.subtract(accRefundAmount)) > 0 ){
            return PayOrderEnums.PAY_ORDER_FUND_AMOUNT_STATUS.PART_FUND.getCode();
        }
        return PayOrderEnums.PAY_ORDER_FUND_AMOUNT_STATUS.SUCCESS.getCode();
    }


    public BigDecimal sum(List<OrderFundDetailDO> fundDetailDOList, PayOrderEnums.PAY_ORDER_FUND_STATUS status){
        BigDecimal amount = new BigDecimal(0);
        for(OrderFundDetailDO orderFundDetailDO:fundDetailDOList){
            if(orderFundDetailDO.getFundStatus() == status.getCode()){
                amount = amount.add(orderFundDetailDO.getAmount() == null? new BigDecimal(0) :orderFundDetailDO.getAmount());
            }
        }
        return amount;
    }

    public static void processQueryDate(BasePageReq basePageReq, BaseQueryDto baseQueryDto) {
        baseQueryDto.setPageNo(basePageReq.getPageNo());
        baseQueryDto.setPageSize(basePageReq.getPageSize());
        if (StringUtils.hasText(basePageReq.getStartTime())) {
            try {
                // LocalDateTime startTime = LocalDateTimeUtil.parse(basePageReq.getStartTime(), DatePattern.NORM_DATETIME_PATTERN);
                Long startTime = DateUtils.shanghaiStartDate(basePageReq.getStartTime(), DatePattern.NORM_DATETIME_PATTERN);
                baseQueryDto.setStartQueryTime(startTime);
            }
            catch (Exception e) {
                try {
                    Long startTime = DateUtils.shanghaiStartDate(basePageReq.getStartTime(), DatePattern.NORM_DATE_PATTERN);
                    baseQueryDto.setStartQueryTime(startTime);
                }
                catch (Exception e1) {
                    try {
                        Long startTime = DateUtils.shanghaiStartDate(basePageReq.getStartTime(), DatePattern.NORM_MONTH_PATTERN);
                        baseQueryDto.setStartQueryTime(startTime);
                    }catch (Exception e2){
                        log.warn("queryPayOrderPage cover start time error:{}", e2.getMessage());
                    }
                }
            }
        }
        if (StringUtils.hasText(basePageReq.getEndTime())) {
            try {
                // LocalDateTime endTime = LocalDateTimeUtil.parse(basePageReq.getEndTime(), DatePattern.NORM_DATETIME_PATTERN);
                Long endTime = DateUtils.shanghaiEndDate(basePageReq.getEndTime(), DatePattern.NORM_DATETIME_PATTERN);
                // + 1 day - 1 s
                baseQueryDto.setEndQueryTime(endTime);
            }
            catch (Exception e) {
                try {
                    // LocalDateTime endTime = LocalDateTimeUtil.parse(basePageReq.getEndTime(), DatePattern.NORM_DATE_PATTERN);
                    Long endTime = DateUtils.shanghaiEndDate(basePageReq.getEndTime(), DatePattern.NORM_DATE_PATTERN);
                    baseQueryDto.setEndQueryTime(endTime);
                }
                catch (Exception e1) {
                    try {
                        // LocalDateTime endTime = LocalDateTimeUtil.parse(basePageReq.getEndTime(), DatePattern.NORM_DATE_PATTERN);
                        Long endTime = DateUtils.shanghaiEndDate(basePageReq.getEndTime(), DatePattern.NORM_MONTH_PATTERN);
                        // + 1 day - 1 s
                        // LocalDateTime localDateTime = endTime.plusDays(1).plusSeconds(-1);
                        baseQueryDto.setEndQueryTime(endTime);
                    }
                    catch (Exception e2) {
                        log.warn("queryPayOrderPage cover end time error:{}", e1.getMessage());
                    }
                }
            }
        }
    }

    /**
     * sn 处理
     * 
     * @param baseAgentPageReq
     * @param baseQueryDto
     */
    public void processQuerySn(BaseAgentPageReq baseAgentPageReq, BaseQueryDto baseQueryDto) {
        LocalAdminUserRequest localAdminUserRequest = (LocalAdminUserRequest) LocalContext.get();
        Integer sysType = localAdminUserRequest.getSysType();
        CommonEnum.SYS_TYPE sys_type = CommonEnum.SYS_TYPE.from(sysType);
        switch (sys_type) {
            case MERCHANT:
                baseQueryDto.setSn(localAdminUserRequest.getSn());
                break;
            case AGENT:
                baseQueryDto.setSnList(localAdminUserRequest.getQuerySnList());
                break;
            case BUSINESS:
                if (StringUtils.hasText(baseAgentPageReq.getAgentId())) {
                    List<String> snList = merchantService.queryMerchantByAgentId(baseAgentPageReq.getAgentId());
                    baseQueryDto.setSnList(snList);
                }
                else if (StringUtils.hasText(baseAgentPageReq.getSn())) {
                    baseQueryDto.setSn(baseAgentPageReq.getSn());
                }
                break;
        }
    }
}
